package org.example.learn.asm.trycatch;

import com.alibaba.fastjson.JSON;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestTryCatch extends ClassLoader {

    public Long strToNumber(String val){
        return Long.valueOf(val);
    }

    public static void output(byte[] bytes) {
        FileOutputStream out = null;
        String path = TestTryCatch.class.getResource("/").getPath()+"TestTryCatch.class";
        try {
            out = new FileOutputStream(path);
            System.out.println("class输出位置：" + path);
            out.write(bytes);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (null != out) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws IOException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        byte[] bytes = new TestTryCatch().getBytes(TestTryCatch.class.getName());
        output(bytes);

        Class<?> clazz = new TestTryCatch().defineClass("org.example.learn.asm.trycatch.TestTryCatch",bytes,0,bytes.length);
        Method method = clazz.getMethod("strToNumber",String.class);

        Object val1 = method.invoke(clazz.newInstance(),"109");
        System.out.println("val1:" + val1);

        Object val2 = method.invoke(clazz.newInstance(),"test");
        System.out.println("val2:" + val2);
    }

    private byte[] getBytes(final String className) throws IOException {
        ClassReader cr = new ClassReader(className);
        ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_MAXS);
        cr.accept(new ClassVisitor(Opcodes.ASM8,cw) {
            @Override
            public MethodVisitor visitMethod(int access, final String name, String descriptor, String signature, String[] exceptions) {

                if (!"strToNumber".equals(name))
                    return super.visitMethod(access, name, descriptor, signature, exceptions);

                MethodVisitor mv = super.visitMethod(access,name,descriptor,signature,exceptions);

                return new AdviceAdapter(Opcodes.ASM8,mv,access,name,descriptor) {

                    private Label from = new Label(), to = new Label(), target = new Label();

                    // 进入方法事件
                    @Override
                    protected void onMethodEnter() {
//                        super.onMethodEnter();
                        // try块 开始位置
                        visitLabel(from);
                        visitTryCatchBlock(from,to,target,"java/lang/Exception");
                    }

                    // 处理堆栈
                    @Override
                    public void visitMaxs(int maxStack, int maxLocals) {

                        // try块结束
                        mv.visitLabel(to);
                        // catch块开始位置
                        mv.visitLabel(target);

                        // 设置堆栈压缩方式
                        mv.visitFrame(Opcodes.F_NEW,0,null,1,new Object[]{"java/lang/Exception"});

                        // 异常信息保存到局部变量
                        int local = newLocal(Type.LONG_TYPE);
                        mv.visitVarInsn(Opcodes.ASTORE,local);

                        // 输出信息

                        // className.methodName
                        mv.visitLdcInsn(className + "." + name);
                        mv.visitVarInsn(ALOAD,local);
                        // 调用其他方法
                        mv.visitMethodInsn(INVOKESTATIC,Type.getInternalName(TestTryCatch.class),"point","(Ljava/lang/String;Ljava/lang/Throwable;)V",false);

                        // 抛出异常
                        mv.visitVarInsn(ALOAD,local);
                        mv.visitInsn(ATHROW);

                        super.visitMaxs(maxStack, maxLocals);
                    }

                    // 方法结束事件
                    @Override
                    protected void onMethodExit(int opcode) {
//                        super.onMethodExit(opcode);
                        if ((IRETURN <= opcode && opcode <= RETURN) || opcode == ATHROW) {
                            int nextLocal = this.nextLocal;
                            // 将栈顶引用类型值保存到局部变量indexbyte中
                            mv.visitVarInsn(ASTORE,nextLocal);
                            // 从局部变量indexbyte中装载引用类型值入栈
                            mv.visitVarInsn(ALOAD,nextLocal);
                            // className.methodName
                            mv.visitLdcInsn(className + "." + name);
                            mv.visitVarInsn(ALOAD,nextLocal);
                            // 调用其他方法
                            mv.visitMethodInsn(INVOKESTATIC,Type.getInternalName(TestTryCatch.class),"point","(Ljava/lang/String;Ljava/lang/Object;)V",false);
                        }
                    }
                };

            }
        },ClassReader.EXPAND_FRAMES);

        return cw.toByteArray();
    }

    public static void point(String methodName,Throwable throwable){
        System.out.println("系统监控::[方法名称:" + methodName + " 异常信息:" + throwable.getMessage() +"]\r\n");
    }

    public static void point(String methodName,Object response){
        System.out.println("系统监控::[方法名称:" + methodName + " 输出信息:" + JSON.toJSONString(response) + "]\r\n");
    }

}
